"""
线损模块业务逻辑处理
"""
from datetime import date, timedelta
from typing import List
from decimal import Decimal

from app.crud import line_loss_crud
from app.schemas.line_loss_schemas import (
    RtuLineLossListRequest,
    RtuLineLossListResponse,
    RtuLineLossItem,
    RtuLineLossDetailResponse,
    MeterConsumptionItem,
    LineLossOverviewResponse
)
from common.database import DatabaseService
from common.exceptions import BizException, ErrorCode
from common.logger import create_logger
from app.common.common import PaginationParams

logger = create_logger("line_loss_service")


class LineLossService:
    """线损业务服务类"""

    @staticmethod
    async def get_rtu_line_loss_list(
        db: DatabaseService,
        request: RtuLineLossListRequest,
        page: PaginationParams
    ) -> RtuLineLossListResponse:
        """
        获取RTU线损列表

        Args:
            db: 数据库服务实例
            request: 查询请求参数
            page: 分页参数

        Returns:
            RtuLineLossListResponse: RTU线损列表响应
        """
        try:
            total, rows = await line_loss_crud.get_rtu_line_loss_list(
                db,
                request,
                page.page,
                page.size
            )

            items = [RtuLineLossItem.model_validate(row) for row in rows]
            pages = (total + page.size - 1) // page.size

            return RtuLineLossListResponse(
                total=total,
                items=items,
                page=page.page,
                size=page.size,
                pages=pages,
                has_next=page.page * page.size < total,
                has_prev=page.page > 1
            )

        except BizException:
            raise
        except Exception as e:
            logger.exception("获取RTU线损列表失败: %s", e)
            raise BizException.from_error_code(
                ErrorCode.INTERNAL_SERVER_ERROR,
                message="获取RTU线损列表失败"
            ) from e

    @staticmethod
    async def get_rtu_line_loss_detail(
        db: DatabaseService,
        rtu_id: int,
        calc_date: date
    ) -> RtuLineLossDetailResponse:
        """
        获取RTU线损详情

        Args:
            db: 数据库服务实例
            rtu_id: RTU设备ID
            calc_date: 计算日期

        Returns:
            RtuLineLossDetailResponse: RTU线损详情响应
        """
        try:
            # 获取线损基本信息
            detail = await line_loss_crud.get_rtu_line_loss_detail(db, rtu_id, calc_date)
            if not detail:
                raise BizException.from_error_code(
                    ErrorCode.NOT_FOUND,
                    message="未找到该RTU的线损数据"
                )

            # 获取电表用电量明细
            meters_data = await line_loss_crud.get_meters_consumption_by_rtu(
                db, rtu_id, calc_date
            )

            meters = [MeterConsumptionItem.model_validate(m) for m in meters_data]

            return RtuLineLossDetailResponse(
                rtu_id=detail["rtu_id"],
                serial_no=detail["serial_no"],
                calc_date=detail["calc_date"],
                loss_rate=detail["loss_rate"],
                loss_kwh=detail["loss_kwh"],
                rtu_consumption=detail["rtu_consumption"],
                meters_consumption=detail["meters_consumption"],
                meter_count=detail["meter_count"],
                valid_meter_count=detail["valid_meter_count"],
                meters=meters
            )

        except BizException:
            raise
        except Exception as e:
            logger.exception("获取RTU线损详情失败: %s", e)
            raise BizException.from_error_code(
                ErrorCode.INTERNAL_SERVER_ERROR,
                message="获取RTU线损详情失败"
            ) from e

    @staticmethod
    async def get_line_loss_overview(
        db: DatabaseService,
        calc_date: date | None = None
    ) -> LineLossOverviewResponse:
        """
        获取线损概览数据

        Args:
            db: 数据库服务实例
            calc_date: 计算日期，默认为昨天

        Returns:
            LineLossOverviewResponse: 线损概览响应
        """
        try:
            if calc_date is None:
                calc_date = date.today() - timedelta(days=1)

            overview = await line_loss_crud.get_line_loss_overview(db, calc_date)

            return LineLossOverviewResponse(
                total_loss_rate=overview["total_loss_rate"],
                total_loss_kwh=overview["total_loss_kwh"]
            )

        except BizException:
            raise
        except Exception as e:
            logger.exception("获取线损概览数据失败: %s", e)
            raise BizException.from_error_code(
                ErrorCode.INTERNAL_SERVER_ERROR,
                message="获取线损概览数据失败"
            ) from e

    @staticmethod
    async def calculate_rtu_line_loss(
        db: DatabaseService,
        rtu_id: int,
        rtu_code: str,
        calc_date: date
    ) -> bool:
        """
        计算单个RTU的线损

        Args:
            db: 数据库服务实例
            rtu_id: RTU设备ID
            rtu_code: RTU设备编码
            calc_date: 计算日期

        Returns:
            bool: 是否计算成功
        """
        try:
            # 1. 获取RTU当日用电量（从dc_meter_telemetry）
            rtu_consumption = await line_loss_crud.get_rtu_consumption_by_date(
                db, rtu_code, calc_date
            )

            if not rtu_consumption or rtu_consumption == 0:
                logger.warning(f"RTU {rtu_id} 在 {calc_date} 无用电量数据")
                return False

            # 2. 获取RTU下所有电表的用电量
            meters_rows = await line_loss_crud.get_meters_consumption_by_rtu_date(
                db, rtu_id, calc_date
            )

            meter_count = len(meters_rows)
            valid_meter_count = sum(1 for m in meters_rows if m["daily_kwh"] and m["daily_kwh"] > 0)
            meters_consumption = sum(Decimal(str(m["daily_kwh"] or 0)) for m in meters_rows)

            # 3. 计算线损
            loss_kwh = rtu_consumption - meters_consumption
            loss_rate = (loss_kwh / rtu_consumption * 100) if rtu_consumption > 0 else Decimal(0)

            # 4. 数据质量判断
            data_quality = "NORMAL"
            if valid_meter_count < meter_count * 0.8:
                data_quality = "PARTIAL"
            if loss_rate > 100 or loss_rate < -20:
                data_quality = "ABNORMAL"

            # 5. 保存结果
            await line_loss_crud.create_rtu_line_loss(
                db,
                rtu_id=rtu_id,
                rtu_code=rtu_code,
                calc_date=calc_date,
                rtu_consumption=Decimal(str(round(rtu_consumption, 4))),
                meters_consumption=Decimal(str(round(meters_consumption, 4))),
                loss_kwh=Decimal(str(round(loss_kwh, 4))),
                loss_rate=Decimal(str(round(loss_rate, 2))),
                meter_count=meter_count,
                valid_meter_count=valid_meter_count,
                data_quality=data_quality
            )

            logger.info(
                f"RTU {rtu_id} 线损计算完成: "
                f"供电量={rtu_consumption}, 售电量={meters_consumption}, "
                f"线损={loss_kwh}, 线损率={loss_rate}%"
            )
            return True

        except BizException:
            raise
        except Exception as e:
            logger.exception(f"计算RTU {rtu_id} 线损失败: %s", e)
            raise BizException.from_error_code(
                ErrorCode.INTERNAL_SERVER_ERROR,
                message=f"计算RTU {rtu_id} 线损失败"
            ) from e
